#
# Copyright (c) 2023 supercell
#
# SPDX-License-Identifier: BSD-3-Clause
#

module Luce
  # Parses preformatted code blocks between two ~~~ or
  # &grave;&grave;&grave; sequences.
  #
  # See the CommonMark spec:
  # https://spec.commonmark.org/0.30/#fenced-code-blocks
  class FencedCodeBlockSyntax < BlockSyntax
    def pattern : Regex
      Luce.code_fence_pattern
    end

    def parse(parser : BlockParser) : Node
      # ameba:disable Lint/NotNil
      opening_fence = FenceMatch.from_match(pattern.match(Luce.escape_punctuation(parser.current.content)).not_nil!)

      text = parse_child_lines(parser, opening_fence.marker, opening_fence.indent).map(&.content).join("\n")

      text = Luce.escape_html(text, escape_apos: false) if parser.document.encode_html?

      text = "#{text}\n" unless text.empty?

      code = Element.text("code", text)
      if opening_fence.has_language?
        language = Luce.decode_html_characters(opening_fence.language)
        language = Luce.escape_html_attribute(language) if parser.document.encode_html?
        code.attributes["class"] = "language-#{language}"
      end

      Element.new("pre", [code] of Node)
    end

    def parse_child_lines(parser : BlockParser, opening_marker : String = "", indent : Int32 = 0) : Array(Line)
      child_lines = [] of Line

      parser.advance

      closing_fence : FenceMatch?

      until parser.done?
        match = pattern.match(parser.current.content)
        if match.nil? || match.begin != 0
          closing_fence = nil
        else
          closing_fence = FenceMatch.from_match(match)
        end

        # Closing code fences cannot have info strings
        # https://spec.commonmark.org/0.30/#example-147
        if closing_fence.nil? || !closing_fence.marker.starts_with?(opening_marker) || closing_fence.has_info?
          child_lines << Line.new(remove_indentation(parser.current.content, indent))
          parser.advance
        else
          parser.advance
          break
        end
      end

      # https://spec.commonmark.org/0.30/#example-127
      # https://spec.commonmark.org/0.30/#example-128
      if closing_fence.nil? && !child_lines.empty? && child_lines.last.is_blank_line?
        child_lines.pop
      end

      child_lines
    end

    private def remove_indentation(content : String, length : Int32) : String
      text = content.sub(/^\s{0,#{length}}/, "")
      content[content.size - text.size..]
    end
  end

  private class FenceMatch
    getter indent : Int32
    getter marker : String

    # The info-string should be trimmed,
    # https://spec.commonmark.org/0.30/#info-string
    getter info : String

    def self.from_match(match : Regex::MatchData)
      marker : String
      info : String

      if match["backtick"]?
        marker = match["backtick"]
        info = match["backtickInfo"]
      else
        marker = match["tilde"]
        info = match["tildeInfo"]
      end

      FenceMatch.new(match[1].size, marker, info.strip)
    end

    def initialize(@indent, @marker, @info)
    end

    # The first word of the info string is typically used to specify the language
    # of the code sample.
    # https://spec.commonmark.org/0.30/#example-143
    def language : String
      info.split(" ")[0]
    end

    def has_info? : Bool
      info.empty? == false
    end

    def has_language? : Bool
      language.empty? == false
    end
  end
end
